//
// Created on 2024/12/9.
//
// Node APIs are not fully supported. To solve the compilation error of the interface cannot be found,
// please include "napi/native_api.h".

#include "pj/math.h"
#include <pjmedia-audiodev/audiodev_imp.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/string.h>
#include <pjmedia/errno.h>
#include <stdbool.h>
#include <ohaudio/native_audiocapturer.h>
#include <ohaudio/native_audiorenderer.h>
#include <ohaudio/native_audiostreambuilder.h>

#define THIS_FILE "oh_dev.c"
#define DRIVER_NAME "OHAudio"
#define NUM_BUFFERS 2

static pj_status_t ohaudio_init(pjmedia_aud_dev_factory *f);
static pj_status_t ohaudio_destroy(pjmedia_aud_dev_factory *f);
static pj_status_t ohaudio_refresh(pjmedia_aud_dev_factory *f);
static unsigned ohaudio_get_dev_count(pjmedia_aud_dev_factory *f);
static pj_status_t ohaudio_get_dev_info(pjmedia_aud_dev_factory *f, unsigned index, pjmedia_aud_dev_info *info);
static pj_status_t ohaudio_default_param(pjmedia_aud_dev_factory *f, unsigned index, pjmedia_aud_param *param);
static pj_status_t ohaudio_create_stream(pjmedia_aud_dev_factory *f, const pjmedia_aud_param *param,
                                         pjmedia_aud_rec_cb rec_cb, pjmedia_aud_play_cb play_cb, void *user_data,
                                         pjmedia_aud_stream **p_aud_strm);

/* Stream prototypes */
static pj_status_t strm_get_param(pjmedia_aud_stream *strm, pjmedia_aud_param *param);
static pj_status_t strm_get_cap(pjmedia_aud_stream *strm, pjmedia_aud_dev_cap cap, void *value);
static pj_status_t strm_set_cap(pjmedia_aud_stream *strm, pjmedia_aud_dev_cap cap, const void *value);
static pj_status_t strm_start(pjmedia_aud_stream *strm);
static pj_status_t strm_stop(pjmedia_aud_stream *strm);
static pj_status_t strm_destroy(pjmedia_aud_stream *strm);

struct ohaudio_aud_factory {
    pjmedia_aud_dev_factory base;
    pj_pool_factory *pf;
    pj_pool_t *pool;
};

/*
 * Sound stream descriptor.
 * This struct may be used for both unidirectional or bidirectional sound
 * streams.
 */
struct oh_audio_aud_stream {
    pjmedia_aud_stream base;

    pj_pool_t *pool;
    pj_str_t name;
    pjmedia_dir dir;
    int bytes_per_sample;
    pj_uint32_t samples_per_sec;
    unsigned samples_per_frame;
    int channel_count;
    pjmedia_aud_param param;

    void *user_data;
    pjmedia_aud_rec_cb rec_cb;
    pjmedia_aud_play_cb play_cb;

    pj_timestamp play_timestamp;
    pj_timestamp rec_timestamp;
    pj_uint32_t underflow;
    pj_uint32_t overflow;

    pj_bool_t quit_flag;

    pj_bool_t rec_thread_exited;
    pj_bool_t rec_thread_initialized;
    pj_thread_desc rec_thread_desc;
    pj_thread_t *rec_thread;

    pj_bool_t play_thread_exited;
    pj_bool_t play_thread_initialized;
    pj_thread_desc play_thread_desc;
    pj_thread_t *play_thread;

    /* Sometime the record callback does not return framesize as configured
     * (e.g: in OSS), while this module must guarantee returning framesize
     * as configured in the creation settings. In this case, we need a buffer
     * for the recorded samples.
     */
    pj_int16_t *rec_buf;
    unsigned rec_buf_count;

    /* Sometime the player callback does not request framesize as configured
     * (e.g: in Linux OSS) while sound device will always get samples from
     * the other component as many as configured samples_per_frame.
     */
    pj_int16_t *play_buf;
    unsigned play_buf_count;

    /* Player */
    OH_AudioRenderer *player_render;
    OH_AudioStreamBuilder *player_builder;

    /* Capture */
    OH_AudioStreamBuilder *capture_builder;
    OH_AudioCapturer *capture_render;
};

static pjmedia_aud_stream_op oh_audio_strm_op = {&strm_get_param, &strm_get_cap, &strm_set_cap,
                                                 &strm_start,     &strm_stop,    &strm_destroy};

static pjmedia_aud_dev_factory_op ohaudio_op = {&ohaudio_init,         &ohaudio_destroy,       &ohaudio_get_dev_count,
                                                &ohaudio_get_dev_info, &ohaudio_default_param, &ohaudio_create_stream,
                                                &ohaudio_refresh};

pj_status_t ohaudio_to_pj_error(OH_AudioStream_Result code) {
    switch (code) {
    case AUDIOSTREAM_SUCCESS:
        return PJ_SUCCESS;
    case AUDIOSTREAM_ERROR_INVALID_PARAM:
        return PJMEDIA_EAUD_INVOP;
    case AUDIOSTREAM_ERROR_ILLEGAL_STATE:
        return PJMEDIA_EAUD_NOTREADY;
    case AUDIOSTREAM_ERROR_SYSTEM:
    default:
        return PJMEDIA_EAUD_ERR;
    }
}

pjmedia_aud_dev_factory *pjmedia_ohaudio_factory(pj_pool_factory *pf) {
    struct ohaudio_aud_factory *f;
    pj_pool_t *pool;

    pool = pj_pool_create(pf, "ohaudio", 256, 256, NULL);
    f = PJ_POOL_ZALLOC_T(pool, struct ohaudio_aud_factory);
    f->pf = pf;
    f->pool = pool;
    f->base.op = &ohaudio_op;
    return &f->base;
}

static pj_status_t ohaudio_init(pjmedia_aud_dev_factory *f) {
    PJ_UNUSED_ARG(f);
    PJ_LOG(4, (THIS_FILE, "OHAduio sound library initialized"));
    return PJ_SUCCESS;
}

static pj_status_t ohaudio_destroy(pjmedia_aud_dev_factory *f) {
    struct ohaudio_aud_factory *pa = (struct ohaudio_aud_factory *)f;
    pj_pool_t *pool;
    PJ_LOG(4, (THIS_FILE, "OHAudio dev sound library shutting down.."));

    pool = pa->pool;
    pa->pool = NULL;
    pj_pool_release(pool);

    return PJ_SUCCESS;
}

/* API: refresh the list of devices */
static pj_status_t ohaudio_refresh(pjmedia_aud_dev_factory *f) {
    PJ_UNUSED_ARG(f);
    return PJ_SUCCESS;
}

/* API: Get device count. */
static unsigned ohaudio_get_dev_count(pjmedia_aud_dev_factory *f) {
    PJ_UNUSED_ARG(f);
    return 1;
}

/* API: Get device info. */
static pj_status_t ohaudio_get_dev_info(pjmedia_aud_dev_factory *f, unsigned index, pjmedia_aud_dev_info *info) {
    PJ_UNUSED_ARG(f);

    pj_bzero(info, sizeof(*info));

    pj_ansi_strxcpy(info->name, "OHAudio", sizeof(info->name));
    pj_ansi_strxcpy(info->driver, DRIVER_NAME, sizeof(info->driver));
    info->default_samples_per_sec = 8000;
    info->caps = PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
    info->input_count = 1;
    info->output_count = 1;

    return PJ_SUCCESS;
}

/* API: fill in with default parameter. */
static pj_status_t ohaudio_default_param(pjmedia_aud_dev_factory *f, unsigned index, pjmedia_aud_param *param) {

    pjmedia_aud_dev_info adi;
    pj_status_t status;

    status = ohaudio_get_dev_info(f, index, &adi);
    if (status != PJ_SUCCESS)
        return status;

    pj_bzero(param, sizeof(*param));
    if (adi.input_count && adi.output_count) {
        param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
        param->rec_id = index;
        param->play_id = index;
    } else if (adi.input_count) {
        param->dir = PJMEDIA_DIR_CAPTURE;
        param->rec_id = index;
        param->play_id = PJMEDIA_AUD_INVALID_DEV;
    } else if (adi.output_count) {
        param->dir = PJMEDIA_DIR_PLAYBACK;
        param->play_id = index;
        param->rec_id = PJMEDIA_AUD_INVALID_DEV;
    } else {
        return PJMEDIA_EAUD_INVDEV;
    }

    param->clock_rate = adi.default_samples_per_sec;
    param->channel_count = 1;
    param->samples_per_frame = adi.default_samples_per_sec * 20 / 1000;
    param->bits_per_sample = 16;
    param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
    param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;

    return PJ_SUCCESS;
}

static OH_AudioData_Callback_Result writePlayerCallback(OH_AudioRenderer *renderer, void *userData, void *audioData,
                                                        int32_t audioDataSize) {
    pj_assert(userData != NULL);

    struct oh_audio_aud_stream *stream = (struct oh_audio_aud_stream *)userData;
    int status = 0;
    unsigned nsamples_req = audioDataSize;

    if (stream->quit_flag)
        goto on_break;


    if (stream->play_thread_initialized == 0 || !pj_thread_is_registered()) {
        pj_bzero(stream->play_thread_desc, sizeof(pj_thread_desc));
        status = pj_thread_register("oh_audio_play", stream->play_thread_desc, &stream->play_thread);
        stream->play_thread_initialized = 1;
        PJ_LOG(5, (THIS_FILE, "Player thread started"));
    }

    pjmedia_frame frame;

    frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
    frame.buf = audioData;
    frame.size = stream->samples_per_frame * stream->bytes_per_sample;
    frame.timestamp.u64 = stream->play_timestamp.u64;
    frame.bit_info = 0;

    status = (*stream->play_cb)(stream->user_data, &frame);
    if (status != PJ_SUCCESS)
        goto on_break;

    if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
        pj_bzero(frame.buf, frame.size);

    return AUDIO_DATA_CALLBACK_RESULT_VALID;

//     /* Check if any buffered samples */
//     if (stream->play_buf_count) {
//         /* samples buffered >= requested by sound device */
//         if (stream->play_buf_count >= nsamples_req) {
//             pjmedia_copy_samples((pj_int16_t *)audioData, stream->play_buf, nsamples_req);
//             stream->play_buf_count -= nsamples_req;
//             pjmedia_move_samples(stream->play_buf, stream->play_buf + nsamples_req, stream->play_buf_count);
//             nsamples_req = 0;
//
//             return AUDIO_DATA_CALLBACK_RESULT_INVALID;
//         }
//
//         /* samples buffered < requested by sound device */
//         pjmedia_copy_samples((pj_int16_t *)audioData, stream->play_buf, stream->play_buf_count);
//         nsamples_req -= stream->play_buf_count;
//         audioData = (pj_int16_t *)audioData + stream->play_buf_count;
//         stream->play_buf_count = 0;
//     }
//
//     while (nsamples_req && status == 0) {
//         if (nsamples_req >= stream->samples_per_frame) {
//             pjmedia_frame frame;
//
//             frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
//             frame.buf = audioData;
//             frame.size = stream->samples_per_frame * stream->bytes_per_sample;
//             frame.timestamp.u64 = stream->play_timestamp.u64;
//             frame.bit_info = 0;
//
//             status = (*stream->play_cb)(stream->user_data, &frame);
//             if (status != PJ_SUCCESS)
//                 goto on_break;
//
//             if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
//                 pj_bzero(frame.buf, frame.size);
//
//             nsamples_req -= stream->samples_per_frame;
//             audioData = (pj_int16_t *)audioData + stream->samples_per_frame;
//         } else {
//             pjmedia_frame frame;
//
//             frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
//             frame.buf = stream->play_buf;
//             frame.size = stream->samples_per_frame * stream->bytes_per_sample;
//             frame.timestamp.u64 = stream->play_timestamp.u64;
//             frame.bit_info = 0;
//
//             status = (*stream->play_cb)(stream->user_data, &frame);
//             if (status != PJ_SUCCESS)
//                 goto on_break;
//
//             if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
//                 pj_bzero(frame.buf, frame.size);
//
//             pjmedia_copy_samples((pj_int16_t *)audioData, stream->play_buf, nsamples_req);
//             stream->play_buf_count = stream->samples_per_frame - nsamples_req;
//             pjmedia_move_samples(stream->play_buf, stream->play_buf + nsamples_req, stream->play_buf_count);
//             nsamples_req = 0;
//         }
//
//         stream->play_timestamp.u64 += stream->samples_per_frame / stream->channel_count;
//     }
//     return AUDIO_DATA_CALLBACK_RESULT_VALID;

on_break:
    stream->play_thread_exited = 1;
    return AUDIO_DATA_CALLBACK_RESULT_INVALID;
}

int32_t readCaptureCallback(OH_AudioCapturer *capturer, void *userData, void *buffer, int32_t length) {

    struct oh_audio_aud_stream *stream = (struct oh_audio_aud_stream *)userData;
    pj_status_t status = 0;
    unsigned nsamples = length;

    if (stream->quit_flag)
        goto on_break;

    if (stream->rec_thread_initialized == 0 || !pj_thread_is_registered()) {
        pj_bzero(stream->rec_thread_desc, sizeof(pj_thread_desc));
        status = pj_thread_register("pa_rec", stream->rec_thread_desc, &stream->rec_thread);
        stream->rec_thread_initialized = 1;
        PJ_LOG(5, (THIS_FILE, "Recorder thread started"));
    }

    pjmedia_frame frame;

    frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
    frame.buf = (void *)buffer;
    frame.size = length;
    frame.timestamp.u64 = stream->rec_timestamp.u64;
    frame.bit_info = 0;

    status = (*stream->rec_cb)(stream->user_data, &frame);
    if (status != PJ_SUCCESS)
        goto on_break;

//     if (nsamples >= stream->samples_per_frame) {
//         /* If buffer is not empty, combine the buffer with the just incoming
//          * samples, then call put_frame.
//          */
//         if (stream->rec_buf_count) {
//             unsigned chunk_count = 0;
//             pjmedia_frame frame;
//
//             chunk_count = stream->samples_per_frame - stream->rec_buf_count;
//             pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count, (pj_int16_t *)buffer, chunk_count);
//
//             frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
//             frame.buf = (void *)stream->rec_buf;
//             frame.size = stream->samples_per_frame * stream->bytes_per_sample;
//             frame.timestamp.u64 = stream->rec_timestamp.u64;
//             frame.bit_info = 0;
//
//             status = (*stream->rec_cb)(stream->user_data, &frame);
//
//             buffer = (pj_int16_t *)buffer + chunk_count;
//             nsamples -= stream->samples_per_frame;
//             stream->rec_buf_count = 0;
//             stream->rec_timestamp.u64 += stream->samples_per_frame / stream->channel_count;
//         }


        /* Give all frames we have */
//         while (nsamples >= stream->samples_per_frame && status == 0) {
//             pjmedia_frame frame;
//
//             frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
//             frame.buf = (void *)buffer;
//             frame.size = stream->samples_per_frame * stream->bytes_per_sample;
//             frame.timestamp.u64 = stream->rec_timestamp.u64;
//             frame.bit_info = 0;
//
//             status = (*stream->rec_cb)(stream->user_data, &frame);
//
//             buffer = (pj_int16_t *)buffer + stream->samples_per_frame;
//             nsamples -= stream->samples_per_frame;
//             stream->rec_timestamp.u64 += stream->samples_per_frame / stream->channel_count;
//         }
//
//         /* Store the remaining samples into the buffer */
//         if (nsamples && status == 0) {
//             stream->rec_buf_count = nsamples;
//             pjmedia_copy_samples(stream->rec_buf, (pj_int16_t *)buffer, nsamples);
//         }

//     } else {
//         /* Not enough samples, let's just store them in the buffer */
//         pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count, (pj_int16_t *)buffer, nsamples);
//         stream->rec_buf_count += nsamples;
//     }

    return 0;

on_break:
    stream->play_thread_exited = 1;
    return -1;
}

int32_t readCaptureInterruptEvent(OH_AudioCapturer *capturer, void *userData, OH_AudioInterrupt_ForceType type,
                                  OH_AudioInterrupt_Hint hint) {
    // 根据type和hint表示的音频中断信息，更新录制器状态和界面
    return 0;
}

/* API: create stream */
static pj_status_t ohaudio_create_stream(pjmedia_aud_dev_factory *f, const pjmedia_aud_param *param,
                                         pjmedia_aud_rec_cb rec_cb, pjmedia_aud_play_cb play_cb, void *user_data,
                                         pjmedia_aud_stream **p_aud_strm) {
    struct ohaudio_aud_factory *oh = (struct ohaudio_aud_factory *)f;

    PJ_ASSERT_RETURN(param->channel_count == 1, PJ_EINVAL);
    PJ_ASSERT_RETURN(play_cb && rec_cb && p_aud_strm, PJ_EINVAL);

    pj_pool_t *pool;
    pjmedia_aud_dev_index rec_id;
    struct oh_audio_aud_stream *stream;

    rec_id = param->rec_id;

    pool = pj_pool_create(oh->pf, "recstrm", 1024, 1024, NULL);
    if (!pool)
        return PJ_ENOMEM;

    stream = PJ_POOL_ZALLOC_T(pool, struct oh_audio_aud_stream);
    stream->pool = pool;
    pj_strdup2_with_null(pool, &stream->name, "OHAudio");
    stream->dir = param->dir;
    stream->user_data = user_data;
    stream->samples_per_sec = param->clock_rate;
    stream->samples_per_frame = param->samples_per_frame;
    stream->bytes_per_sample = param->bits_per_sample / 8;
    stream->channel_count = param->channel_count;
    stream->rec_cb = rec_cb;
    stream->play_cb = play_cb;

    stream->rec_buf = (pj_int16_t *)pj_pool_alloc(pool, stream->samples_per_frame * stream->bytes_per_sample);
    stream->rec_buf_count = 0;

    OH_AudioStream_Result result;

    pj_status_t status = PJ_SUCCESS;

    if (param->dir & PJMEDIA_DIR_PLAYBACK) {

        OH_AudioStream_Result result;

        OH_AudioStreamBuilder *builder;
        OH_AudioStreamBuilder_Create(&builder, AUDIOSTREAM_TYPE_RENDERER);
        OH_AudioStreamBuilder_SetSamplingRate(builder, param->clock_rate);
        OH_AudioStreamBuilder_SetChannelCount(builder, param->channel_count);
        OH_AudioStreamBuilder_SetSampleFormat(builder, AUDIOSTREAM_SAMPLE_S16LE);
        OH_AudioStreamBuilder_SetEncodingType(builder, AUDIOSTREAM_ENCODING_TYPE_RAW);

        OH_AudioRenderer_Callbacks callbacks;
        OH_AudioRenderer_OnWriteDataCallback writeDataCb = writePlayerCallback;
        OH_AudioRenderer *audioRenderer;

        // 关键参数，仅OHAudio支持，根据音频用途设置，系统会根据此参数实现音频策略自适应
        result = OH_AudioStreamBuilder_SetRendererInfo(builder, AUDIOSTREAM_USAGE_VOICE_COMMUNICATION);
        if (result != AUDIOSTREAM_SUCCESS) {
            PJ_LOG(3, (THIS_FILE, "Cannot create render info: %d", result));
            goto on_error;
        }

        // 设置输出音频流的回调，在生成音频播放对象时自动注册
        result = OH_AudioStreamBuilder_SetRendererCallback(builder, callbacks, stream);

        if (result != AUDIOSTREAM_SUCCESS) {
            PJ_LOG(3, (THIS_FILE, "Cannot register player callback"));
            goto on_error;
        }

        OH_AudioStreamBuilder_SetFrameSizeInCallback(builder, param->samples_per_frame);

        result = OH_AudioStreamBuilder_SetRendererWriteDataCallback(builder, writeDataCb, stream);
        if (result != AUDIOSTREAM_SUCCESS) {
            PJ_LOG(3, (THIS_FILE, "Cannot register player callback"));
            goto on_error;
        }

        result = OH_AudioStreamBuilder_GenerateRenderer(builder, &stream->player_render);
        if (result != AUDIOSTREAM_SUCCESS) {
            PJ_LOG(3, (THIS_FILE, "Cannot create render audio: %d", result));
            goto on_error;
        }

        stream->play_buf = (pj_int16_t *)pj_pool_alloc(pool, stream->samples_per_frame * stream->bytes_per_sample);
        stream->play_buf_count = 0;
        stream->player_builder = builder;
    }

    if (param->dir & PJMEDIA_DIR_CAPTURE) {

        OH_AudioStream_Result result;

        OH_AudioStreamBuilder *builder;
        result = OH_AudioStreamBuilder_Create(&builder, AUDIOSTREAM_TYPE_CAPTURER);
        if (result != AUDIOSTREAM_SUCCESS) {
            PJ_LOG(3, (THIS_FILE, "Cannot create capture builder: %d", result));
            goto on_error;
        }

        OH_AudioStreamBuilder_SetSamplingRate(builder, param->clock_rate);
        OH_AudioStreamBuilder_SetChannelCount(builder, param->channel_count);
        OH_AudioStreamBuilder_SetSampleFormat(builder, AUDIOSTREAM_SAMPLE_S16LE);
        OH_AudioStreamBuilder_SetEncodingType(builder, AUDIOSTREAM_ENCODING_TYPE_RAW);
        OH_AudioStreamBuilder_SetCapturerInfo(builder, AUDIOSTREAM_SOURCE_TYPE_MIC);

        OH_AudioCapturer_Callbacks callbacks;

        // 使用前，初始化并清零结构体
        memset(&callbacks, 0, sizeof(OH_AudioCapturer_Callbacks));

        callbacks.OH_AudioCapturer_OnReadData = readCaptureCallback;
        callbacks.OH_AudioCapturer_OnInterruptEvent = readCaptureInterruptEvent;
        callbacks.OH_AudioCapturer_OnStreamEvent = NULL;
        callbacks.OH_AudioCapturer_OnError = NULL;

        OH_AudioStreamBuilder_SetFrameSizeInCallback(builder, param->samples_per_frame);

        result = OH_AudioStreamBuilder_SetCapturerCallback(builder, callbacks, stream);
        if (result != AUDIOSTREAM_SUCCESS) {
            PJ_LOG(3, (THIS_FILE, "Cannot create capture callback: %d", result));
            goto on_error;
        }
        result = OH_AudioStreamBuilder_GenerateCapturer(builder, &stream->capture_render);
        if (result != AUDIOSTREAM_SUCCESS) {
            PJ_LOG(3, (THIS_FILE, "Cannot create capture: %d", result));
            goto on_error;
        }

        stream->capture_builder = builder;
    }

    if (status != PJ_SUCCESS)
        return status;

    stream->base.op = &oh_audio_strm_op;
    *p_aud_strm = &stream->base;

    return PJ_SUCCESS;

on_error:
    return ohaudio_to_pj_error(result);
}

/* API: Get stream parameters */
static pj_status_t strm_get_param(pjmedia_aud_stream *s, pjmedia_aud_param *pi) {
    struct oh_audio_aud_stream *strm = (struct oh_audio_aud_stream *)s;
    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
    pj_memcpy(pi, &strm->param, sizeof(*pi));

    if (strm_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, &pi->output_vol) == PJ_SUCCESS) {
        pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
    }

    return PJ_SUCCESS;
}

/* API: get capability */
static pj_status_t strm_get_cap(pjmedia_aud_stream *s, pjmedia_aud_dev_cap cap, void *pval) {
    struct oh_audio_aud_stream *strm = (struct oh_audio_aud_stream *)s;
    pj_status_t status = PJMEDIA_EAUD_INVCAP;

    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);

    if (cap == PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING && (strm->param.dir & PJMEDIA_DIR_PLAYBACK)) {
    }

    return status;
}

/* API: set capability */
static pj_status_t strm_set_cap(pjmedia_aud_stream *s, pjmedia_aud_dev_cap cap, const void *value) {
    struct oh_audio_aud_stream *strm = (struct oh_audio_aud_stream *)s;

    PJ_ASSERT_RETURN(s && value, PJ_EINVAL);

    if (cap == PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING && (strm->param.dir & PJMEDIA_DIR_PLAYBACK)) {
        float vol = *(int *)value;
        // TODO: - ? 确定 vol 的值是否是 0.0-1.0？
        OH_AudioStream_Result res = OH_AudioRenderer_SetVolume(strm->player_render, vol);
        if (res == AUDIOSTREAM_SUCCESS) {
            return PJ_SUCCESS;
        }
    }

    return PJMEDIA_EAUD_INVCAP;
}

/* API: start stream. */
static pj_status_t strm_start(pjmedia_aud_stream *s) {
    struct oh_audio_aud_stream *stream = (struct oh_audio_aud_stream *)s;
    int i;
    OH_AudioStream_Result res = AUDIOSTREAM_SUCCESS;
    PJ_LOG(4, (THIS_FILE, "Starting %s stream..", stream->name.ptr));
    stream->quit_flag = 0;

    if (stream->capture_render) {
        res = OH_AudioCapturer_Start(stream->capture_render);
        if (res != AUDIOSTREAM_SUCCESS) {
            PJ_LOG(3, (THIS_FILE, "Cannot start capture"));
            goto on_error;
        }
    }

    // play
    if (stream->player_render) {
        res = OH_AudioRenderer_Start(stream->player_render);
        if (res != AUDIOSTREAM_SUCCESS) {
            PJ_LOG(3, (THIS_FILE, "Cannot start player"));
            goto on_error;
        }
    }
    PJ_LOG(4, (THIS_FILE, "%s stream started", stream->name.ptr));
    return PJ_SUCCESS;

on_error:
    if (res != AUDIOSTREAM_SUCCESS)
        strm_stop(&stream->base);
    return ohaudio_to_pj_error(res);
}

/* API: stop stream. */
static pj_status_t strm_stop(pjmedia_aud_stream *s) {
    struct oh_audio_aud_stream *stream = (struct oh_audio_aud_stream *)s;

    PJ_LOG(4, (THIS_FILE, "Stopping stream"));

    stream->quit_flag = 1;

    OH_AudioStream_Result res = AUDIOSTREAM_SUCCESS;

    if (stream->player_render) {
        res = OH_AudioRenderer_Stop(stream->player_render);
        if (res != AUDIOSTREAM_SUCCESS) {
            PJ_LOG(3, (THIS_FILE, "Cannot stop player"));
        }
    }

    if (stream->capture_render) {
        res = OH_AudioCapturer_Stop(stream->capture_render);
        if (res != AUDIOSTREAM_SUCCESS) {
            PJ_LOG(3, (THIS_FILE, "Cannot stop capture"));
        }
    }

    PJ_LOG(4, (THIS_FILE, "OHAudio stream stopped"));

    return ohaudio_to_pj_error(res);
}

/* API: destroy stream. */
static pj_status_t strm_destroy(pjmedia_aud_stream *s) {
    struct oh_audio_aud_stream *stream = (struct oh_audio_aud_stream *)s;

    /* Stop the stream */
    strm_stop(s);

    if (stream->player_render && stream->player_builder) {
        OH_AudioRenderer_Release(stream->player_render);
        OH_AudioStreamBuilder_Destroy(stream->player_builder);
        stream->player_render = NULL;
        stream->player_builder = NULL;
    }

    if (stream->capture_render && stream->capture_builder) {
        OH_AudioCapturer_Release(stream->capture_render);
        OH_AudioStreamBuilder_Destroy(stream->capture_builder);
        stream->capture_render = NULL;
        stream->capture_builder = NULL;
    }

    pj_pool_release(stream->pool);
    PJ_LOG(4, (THIS_FILE, "OHAudio stream destroyed"));

    return PJ_SUCCESS;
}
